//
//  JavascriptProject.m
//  iCodeMac
//
//  Created by Andrew Barry on 16/03/09.
//  Copyright 2009 Barry Computer Services. All rights reserved.
//

#import "JavascriptProject.h"
// #import "Linker.h"
#import "jvm.h"
#import "Debugger.h"
#import "ProjectWindowController.h"
#import "DebugConsole.h"
#import "DebugConnection.h"
#import "MacAppTarget.h"
#import "WebTarget.h"
#import "WrappedJsval.h"
#import "JavascriptForm.h"
#import "JavascriptSource.h"
#import "AppLoader.h"
#include "mersenneTwister.h"

extern NSDictionary *debugPanelDefn;

NSString *buildRelativePath(NSString *baseFolder, NSString * path) {
	NSString *baseFolderSlash = [baseFolder stringByAppendingString:@"/"];
	NSString *result = @"";
	
	while (1) {
		if ([path length] > [baseFolderSlash length] && [[path substringToIndex:[baseFolderSlash length]] isEqualToString:baseFolderSlash]) {
			return [result stringByAppendingString:[path substringFromIndex:[baseFolderSlash length]]];
		}
		baseFolder = [baseFolder stringByDeletingLastPathComponent];
		baseFolderSlash = [baseFolder stringByAppendingString:@"/"];
		result = [result stringByAppendingString:@"../"];
	}
}

NSString *applyRelative(NSString *baseFolder, NSString *relative) {
	while ([relative length] > 3 && [[relative substringToIndex:3] isEqualToString:@"../"]) {
		relative = [relative substringFromIndex:3];
		if ([baseFolder isEqualToString:@"/"])
			return nil;
		baseFolder = [baseFolder stringByDeletingLastPathComponent];
	}
	return [baseFolder stringByAppendingFormat:@"/%@", relative];
}


@implementation JavascriptProject

+(JavascriptProject *) projectForDocument: (NSDocument *) doc {
	NSURL *docURL = [doc fileURL];
	if (!docURL)
		return nil;
	
	if (![docURL isFileURL])
		return nil;
	NSString *docPath = [docURL path];
	NSString *docFolder = [docPath stringByDeletingLastPathComponent];
	NSString *docFile = [docPath lastPathComponent];
	
	NSDocumentController *docController = [NSDocumentController sharedDocumentController];
	NSArray *docs = [docController documents];
	for (int idx = 0; idx < [docs count]; idx++) {
		NSDocument *doc = [docs objectAtIndex:idx];
		if ([doc isKindOfClass:[JavascriptProject class]]) {
			JavascriptProject *proj = (JavascriptProject *) doc;
			if ([[proj projectFolder] isEqualToString:docFolder]) {
				NSArray *items = [proj items];
				for (int subidx = 0; subidx < [items count]; subidx++) {
					NSString *name = [items objectAtIndex:subidx];
					if ([name isEqualToString:docFile])
						return proj;
				}
			}
		}
	}
	return nil;
}

-(void) makeWindowControllers {
    ProjectWindowController *ctl = 
	[ [ProjectWindowController alloc] initWithWindowNibName:@"ProjectWindow" ];
    [ctl autorelease];
    [self addWindowController:ctl];
}

- (NSString *)windowNibName {
    return @"ProjectWindow";
}

-(void) resolveItemPaths {
	for (int idx = 0; idx < [projectItems count]; idx++) {
		id item = [projectItems objectAtIndex:idx];
		if ([item isKindOfClass:[NSDictionary class]]) {
			NSMutableDictionary *dict = item;
			NSString *path = [self pathForItemIndex:idx];
			if (path != nil && [[NSFileManager defaultManager] fileExistsAtPath:path]) {
				[dict setObject:path forKey:@"absolute"];
				[dict setObject:buildRelativePath([self projectFolder], path) forKey:@"relative"];
			}
		}
	}
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
	NSMutableDictionary *save = [NSMutableDictionary dictionaryWithCapacity:1];
	
	[self resolveItemPaths];
	
	[save setObject:projectItems forKey:@"items"];
	if (settings)
		[save setObject:settings forKey:@"settings"];
	if (projType)
		[save setObject:projType forKey:@"projType"];

    return [NSPropertyListSerialization dataFromPropertyList:save format:NSPropertyListXMLFormat_v1_0 errorDescription:nil];;
}

-(void) linkToProgram: (NSString *) path {
	NSMutableDictionary *save = [NSMutableDictionary dictionaryWithCapacity:1];
	NSMutableArray *compiledItems = [NSMutableArray new];
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		[compiledItems addObject:[path lastPathComponent]];
	}
	
	[save setObject:compiledItems forKey:@"items"];
	
	if (settings) {
		if ([[settings objectForKey:@"enableDatabase"] intValue]) {
			[save setObject:[settings objectForKey:@"dbDriverJar"] forKey:@"jdbcdriver"];
			[save setObject:[settings objectForKey:@"dbDriverClass"] forKey:@"jdbcclass"];
			[save setObject:[settings objectForKey:@"dbConnection"] forKey:@"jdbconnection"];
		}
	}
	[save writeToFile:path atomically:NO];
}

-(jvm*) jvmInstance {
	return [jvm instance];
}


-(bool) isOSXProject {
	return (projType == nil || [projType isEqualToString:@"osxapp"] || [projType isEqualToString:@"osxtool"]);
}

-(bool) isOSXApplication {
	return (projType == nil || [projType isEqualToString:@"osxapp"]);
}

-(bool) isWin32Project {
	return (projType != nil && [projType isEqualToString:@"win32app"] || [projType isEqualToString:@"win32tool"]);
}

-(bool) isPadProject {
	return (projType != nil && [projType isEqualToString:@"ipadapp"]);
}

-(bool) isWebProject {
	return (projType != nil && [projType isEqualToString:@"webapp"]);
}

-(bool) isConsoleProject {
	return (projType != nil && ([projType isEqualToString:@"osxtool"] || [projType isEqualToString:@"win32tool"]));
}

-(BOOL) databaseEnabled {
	if ([[settings objectForKey:@"enableDatabase"] intValue]) {
		return YES;
	} else {
		return NO;
	}
}

-(NSString *) appVersion {
	NSString *ver = [settings objectForKey:@"appVersion"];
	if (!ver)
		return @"1.0";
	else
		return ver;
}

-(NSString *) randomAppIdentifier {
	return [NSString stringWithFormat:@"com.app%d", mt_random()];
}

-(NSString *) appIdentifier {
	NSString *ident = [settings objectForKey:@"appIdentifier"];
	if (!ident)
		return [self randomAppIdentifier];
	return ident;
}

-(NSString *) iconFileName {
	int count = [self projectItemCount];
	for (int idx = 0; idx < count; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		NSString *name = [path lastPathComponent];
		NSString *ext = [path pathExtension];
		if ([ext isEqualToString:@"icns"]) {
			return name;
		}
	}

	return nil;
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
	NSPropertyListFormat fmt;
	
	saved = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainers format:&fmt errorDescription:nil];

	projectItems = [NSMutableArray new];
	
	NSArray *savedItems = [saved objectForKey:@"items"];
	if (savedItems != nil) {
		for (int idx = 0; idx < [savedItems count]; idx++)
			[projectItems addObject:[savedItems objectAtIndex:idx]];
	}
	
	projType = [saved objectForKey:@"projType"];
	if (!projType)
		projType = @"osxapp";
	[projType retain];
	
	NSDictionary *savedSettings = [saved objectForKey:@"settings"];
	if (savedSettings != nil) {
		settings = [[NSMutableDictionary dictionaryWithDictionary:savedSettings] retain];
	} else {
		settings = [NSMutableDictionary new];
    }
	
	return YES;
}

-(NSString *)pathForItemIndex: (int) idx {
	id item = [projectItems objectAtIndex:idx];
	
	if ([item isKindOfClass:[NSString class]]) {	
		NSURL *projURL = [self fileURL];
		if ([projURL isFileURL]) {
			NSString *projPath = [projURL path];
			NSString *docPath = [[projPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:item];
			return docPath;
		}
	} else {
		NSDictionary *dict = item;
		NSString *relative = [dict objectForKey:@"relative"];
		NSString *path = applyRelative([self projectFolder], relative);
		if (path != nil) {
			if ([[NSFileManager defaultManager] fileExistsAtPath:path])
				return path;
		}
		return [dict objectForKey:@"absolute"];
	}
	return nil;
}

-(NSString *)pathTo: (NSString *)name {
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		if ([name isEqualToString:[path lastPathComponent]])
			return path;
	}
	return nil;
}


-(NSString *) buildPath {
	NSURL *projURL = [self fileURL];
	if ([projURL isFileURL]) {
		NSString *projPath = [projURL path];
		
		NSString *basePath = [projPath stringByDeletingPathExtension];
		if (!buildingForRelease)
			basePath = [basePath stringByAppendingString: @" (debug)"];
		
		if ([self isWebProject])
			return [basePath stringByAppendingPathExtension:@"html"];
		else if ([self isWin32Project])
			return [basePath stringByAppendingPathExtension:@"exe"];
		else if ([self isConsoleProject])
			return basePath;
		else
			return [basePath stringByAppendingPathExtension:@"app"];
	}
	return nil;
}

-(NSString *) projectFolder {
	NSURL *projURL = [self fileURL];
	if ([projURL isFileURL]) {
		NSString *projPath = [projURL path];
		return [projPath stringByDeletingLastPathComponent];
	}
	return nil;
}

-(NSString *) buildName {
	NSURL *projURL = [self fileURL];
	if ([projURL isFileURL]) {
		NSString *projPath = [projURL path];
		return [[projPath lastPathComponent] stringByDeletingPathExtension];
	}
	return nil;
}

-(bool) precompilePath: (NSString *) path {
	NSString *ext = [path pathExtension];
	if ([ext isEqualToString:@"jsfrm"]) {
		return [JavascriptForm precompileForm: path];
	}
	if ([ext isEqualToString:@"js"]) {
		return [JavascriptSource precompileSource: path];
	}
	return true;
}

-(bool) precompile {
	[self clearStop];
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		if (![self precompilePath: path])
			return false;
	}
	return true;
}

-(void)	displayCompilationError: (NSString *) errorMessage forFile: (NSString *) path forControl: (NSString *) ctlName forEvent: (NSString *) eventName atLine: (int) line {
	if (!path) {
		return;
	}
	NSURL *docURL = [NSURL fileURLWithPath:path];
	NSObject *sourceDoc = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:docURL display:YES error: nil];
	
	if (sourceDoc == nil) {
		NSAlert *alert = [NSAlert alertWithMessageText:@"Error" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:[NSString stringWithFormat:@"%@\n%@\nLine %d", errorMessage, path, line]];
		[alert runModal];
		return;
	}
	
	if ([sourceDoc isKindOfClass:[JavascriptForm class]]) {
		JavascriptForm *sourceForm = (JavascriptForm *) sourceDoc;
		if (ctlName && ![ctlName isEqualToString:@"_form"])
			[sourceForm selectPopupControl:[sourceForm controlByName: ctlName]];
		else
			[sourceForm selectPopupControl:nil];
		[sourceForm selectEvent:eventName];
		[sourceForm updateCurrentEvent];
		[sourceForm showError:errorMessage atLine:line];
	} else if ([sourceDoc isKindOfClass:[JavascriptSource class]]) {
		JavascriptSource *source = (JavascriptSource *) sourceDoc;
		[source showError:errorMessage atLine:line forScript:path];
	}
}
/*
-(bool)buildApplicationForRelease: (bool) forRelease chainTo: (SEL) chain {
	buildingForRelease = forRelease;
	if (![self precompile])
		return false;
	Linker *linker = [Linker new];
	if (forRelease)
		[linker setOptimise];
	bool rv = [linker linkProject:self chainTo: chain];
	if (!rv && ![linker errorShown]) {
		[self showError: [linker errorMessage] withHeader: @"Build error"];
	}
	return rv;
}
*/
-(void)clearStop {
	[currentStopFilename autorelease];
	currentStopFilename = nil;
	currentStopLine = 0;
	isStopped = false;
	
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
		NSDocument *doc = [docControl documentForURL: [NSURL fileURLWithPath:path]];
		if (doc != nil && [doc respondsToSelector:@selector(clearStop)]) {
			[(id)doc clearStop];
		}
	}
	
}

-(NSString *)currentStopFilename {
	return currentStopFilename;
}

-(int)currentStopLine {
	return currentStopLine;
}

-(void) saveBreakpoints {
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
		NSDocument *doc = [docControl documentForURL: [NSURL fileURLWithPath:path]];
		if (doc != nil && [doc respondsToSelector:@selector(saveBreakpoints)]) {
			[(id)doc saveBreakpoints];
		}
	}
}

-(void)sendResume: (NSString *) action {
	NSMutableDictionary *dict = [NSMutableDictionary new];
	[dict setObject:action forKey:@"action"];
	
	[self saveBreakpoints];
	if (breakpointsChanged) {
		NSMutableDictionary *breakpoints = [userSettings objectForKey:@"breakpoints"];
		if (breakpoints) {
			breakpointsChanged = false;
			[dict setObject:breakpoints forKey:@"breakpoints"];
		}
	}
	
	[debugConnection sendPacket:dict];
	[self clearStop];
}

-(void)stepInto: (id) sender {
	if (debugConnection != nil) {
		if (isStopped) {
			isStopped = false;
			[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];

			if (debugPanel) {
				[debugPanel invoke: @"hide" args: nil];
			}
			[self sendResume:@"stepInto"];
		}
	}
}

-(void)stepOut: (id) sender {
	if (debugConnection != nil) {
		if (isStopped) {
			isStopped = false;
			[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];
			if (debugPanel) {
				[debugPanel invoke: @"hide" args: nil];
			}
			[self sendResume:@"stepOut"];
		}
	}
}

-(void)stepOver: (id) sender {
	if (debugConnection != nil) {
		if (isStopped) {
			isStopped = false;
			[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];
			if (debugPanel) {
				[debugPanel invoke: @"hide" args: nil];
			}
			[debugConnection sendCommand:@"stepOver"];
			[self clearStop];
		}
	}
}

-(void) saveAll {
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];
		NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
		NSDocument *doc = [docControl documentForURL: [NSURL fileURLWithPath:path]];
		if (doc != nil) {
			[doc saveDocument:self];
		}
	}
}

-(void)buildApplication: (id) sender {
	// save everything first
	[self saveAll];
	
	[self buildApplicationForRelease: true chainTo: @selector(showBuiltApp)];	
}

-(void) showBuiltApp {
	NSWorkspace *ws = [NSWorkspace sharedWorkspace];
	NSString *path = [self buildPath];
	[ws selectFile: path inFileViewerRootedAtPath:nil];
}

-(void)debugRun: (id) sender {
	if (debugConnection != nil) {
		if (isStopped) {
			[self sendResume:@"continue"];
			
			isStopped = false;
			[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];

			if (debugPanel) {
				[debugPanel invoke: @"hide" args: nil];
			}
			
		
		} else
			[debugConnection sendCommand:@"front"];
		return;
	}
	// save everything first
	[self saveAll];
	
	id handler = [AppLoader handlerForProjectType: projType];
	if (handler)
		[handler run: self];
	
//	[self buildApplicationForRelease: false chainTo: @selector(runBuiltApp)];
}

-(void) runBuiltApp {
	if ([self isWebProject]) {
		[[NSWorkspace sharedWorkspace] openFile: [self buildPath]];
	} else {
		[[DebugLauncher sharedLauncher] launch:[self buildPath]];
	}
}

-(void)debugStop:(id)sender {
	if (debugConnection != nil) {
		[debugConnection sendCommand:@"kill"];
		[self clearStop];
	}
}

-(NSString *)debugKey {
	return debugKey;
}

-(void) setDebugKey: (NSString *) key {
	[debugKey autorelease];
	debugKey = [key retain];
}

-(NSMutableArray *) items {
	return projectItems;
}

-(int) projectItemCount {
	return [projectItems count];
}

-(void) startedDebugging: (DebugConnection *) sender {
	debugConnection = sender;
	
	WrappedJsval *sys = [WrappedJsval wrap:mainContext->slots[13] withContext:mainContext];
	debugPanel = [[sys invoke: @"newDebugPanel" args: nil] retain];
	/*
	FormLoader *loader = [FormLoader new];
	
	NSWindow *wnd = [loader newPanel:debugPanelDefn];
	JSObject *wndObj = [loader loadForm:debugPanelDefn into:wnd];
	debugPanel = [[WrappedJsval alloc] initWithVal:OBJECT_TO_JSVAL(wndObj)];
	[loader startForm: wndObj];

	jsval jsDebugKey = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, [debugKey UTF8String]));
	JS_SetProperty(cx, wndObj, "debugKey", &jsDebugKey);
*/
	[debugPanel setObject: debugKey forKey: @"debugKey"];
	
	[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];
	
	NSMutableDictionary *dict = [NSMutableDictionary new];
	[dict setObject:@"accept" forKey:@"action"];
	
	if (!userSettings)
		[self loadUserSettings];

	NSMutableDictionary *breakpoints = [userSettings objectForKey:@"breakpoints"];
	
	if (breakpoints)
		[dict setObject:breakpoints forKey:@"breakpoints"];
	[debugConnection sendPacket: dict];
	[self updateEditing];
}

-(void) debugEnded: (DebugConnection *) sender {
	debugConnection = nil;
	[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];
	[self updateEditing];
	
	if (debugPanel) {
		[debugPanel invoke: @"closeSession" args: nil];
		[debugPanel release];
		debugPanel = nil;
	}
}

-(void) debugFatalError: (id) packet {
}

-(void) debugStr: (NSString *) msg {
	[DebugConsole logMessage:msg];
}

-(NSDocument *) displaySource: (NSString*) filename atLine: (int) lineno {
	if (currentStopFilename)
		[currentStopFilename autorelease];
	currentStopFilename = [filename retain];
	currentStopLine = lineno;
	
	NSDocument *sourceDoc = nil;

	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *docPath = [self pathForItemIndex:idx];
		NSString *item = [docPath lastPathComponent];
		if ([item isEqualToString:filename]) {
			NSURL *docURL = [NSURL fileURLWithPath:docPath];
			sourceDoc = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:docURL display:YES error: nil];
			break;
		} else if ([filename hasPrefix:[item stringByAppendingString:@"/"]]) {
			NSURL *docURL = [NSURL fileURLWithPath:docPath];
			sourceDoc = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:docURL display:YES error: nil];
			break;
		}
	}
	
	for (int idx = 0; idx < [self projectItemCount]; idx++) {
		NSString *path = [self pathForItemIndex:idx];

		NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
		NSDocument *doc = [docControl documentForURL: [NSURL fileURLWithPath:path]];
		if (doc != nil) {
			if (doc != sourceDoc) {
				if ([doc respondsToSelector:@selector(clearStop)]) {
					[(id)doc clearStop];
				}
			} else {
				if ([doc respondsToSelector:@selector(setRunMaker:)]) {
					[(id)doc setRunMaker:lineno];
				}
			}
		}
	}
	
	return sourceDoc;
}

-(void) debugger: (NSDictionary *) packet {
	isStopped = true;
	[[[self windowControllers] objectAtIndex:0] synchronizeWindowTitleWithDocumentName];
	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
	
	NSArray *stack = [packet objectForKey:@"stack"];
	
	NSString *message = [packet objectForKey: @"message"];
	NSString *filename = [packet objectForKey:@"filename"];
	int lineno = [[packet objectForKey:@"lineno"] intValue];

	if (debugPanel) {
		[debugPanel invoke: @"show" args: nil];
		[debugPanel invoke: @"enter" args: nil];
	}

	if (stack) {
		for (int idx = 0; idx < [stack count]; idx++) {
			NSDictionary *dict = [stack objectAtIndex:idx];
			NSString *script = [dict objectForKey:@"script"];
			int nLine = [[dict objectForKey:@"line"] intValue];

			NSArray *varNames = [dict objectForKey:@"varNames"];
			NSArray *varValues = [dict objectForKey:@"varValues"];

			jsvalue varsval = JSVAL_VOID;
			jsvalue valsval = JSVAL_VOID;
			
			jsaddroot(mainContext, &varsval);
			jsaddroot(mainContext, &valsval);
			
			varsval = jsnewarray(mainContext);
			valsval = jsnewarray(mainContext);
						
			if (varNames != nil) {
				for (int kidx = 0; kidx < [varNames count] && kidx < [varValues count]; kidx++) {
					NSString *keyName = [varNames objectAtIndex:kidx];
					NSString *keyValue = [varValues objectAtIndex:kidx];
					
					NSString *fmt = [NSString stringWithFormat:@"%@=%@", keyName, keyValue];
					
					jspush(mainContext, varsval, jsstrFromNSString(mainContext, fmt));
				}
			}
			
			if (idx == 0) {
				filename = script;
				lineno = nLine;
			}
			
			if (debugPanel) {
				[debugPanel invoke:@"addStack" args:[NSArray arrayWithObjects: script, [NSNumber numberWithInt:nLine], [WrappedJsval wrap:varsval withContext:mainContext], nil]];
				
/*				
				jsvalue rv = JSVAL_VOID;
				jsvalue params[3];
				
				params[0] = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, [script UTF8String]));
				params[1] = INT_TO_JSVAL(nLine);
				params[2] = varsval;
				
				JS_CallFunctionName(cx, JSVAL_TO_OBJECT( debugPanel->val), "addStack", 3, params, &rv);
 */
			}
			
			jsremoveroot(mainContext, &varsval);
			jsremoveroot(mainContext, &valsval);
		}
		
		if (debugPanel) {
			[debugPanel invoke: @"loaded" args: nil];
		}
	}

	if (filename) {		
		id doc = [self displaySource: filename atLine: lineno];
		[doc showError: message atLine: lineno forScript: filename];
	} else {
		
	}
}

-(void)updateEditing {
	NSDocumentController *docController = [NSDocumentController sharedDocumentController];
	NSArray *docs = [docController documents];
	for (int idx = 0; idx < [docs count]; idx++) {
		NSDocument *doc = [docs objectAtIndex:idx];
		if ([doc respondsToSelector:@selector(updateEditEnabled)])
			[(id)doc updateEditEnabled];
	}
}

-(bool) isDebugging {
	return debugConnection != nil;
}

-(bool) isStopped {
	return isStopped;
}

-(void) loadUserSettings {
	NSURL *projURL = [self fileURL];
	if ([projURL isFileURL]) {
		NSString *projPath = [projURL path];
		NSString *settingsFile = [projPath stringByAppendingString:@".user"];
		
		if ([[NSFileManager defaultManager] fileExistsAtPath:settingsFile]) {
			userSettings = [[NSMutableDictionary alloc] initWithContentsOfFile:settingsFile];
			return;
		}
	}
	userSettings = [[NSMutableDictionary alloc] init];
}

-(void) saveUserSettings {
	NSURL *projURL = [self fileURL];
	if ([projURL isFileURL]) {
		NSString *projPath = [projURL path];
		NSString *settingsFile = [projPath stringByAppendingString:@".user"];
		
		[userSettings writeToFile:settingsFile atomically:YES];
	}
}

-(void) saveFrame: (NSRect) f forWindow: (NSString *) name {
	if (!userSettings)
		[self loadUserSettings];
	
	NSMutableDictionary *frames = [userSettings objectForKey:@"frames"];
	if (!frames) {
		frames = [NSMutableDictionary new];
		[userSettings setObject:frames forKey:@"frames"];
	}

	NSDictionary *fdict = [NSDictionary dictionaryWithObjectsAndKeys:
							[NSNumber numberWithInt:f.origin.x], @"x",
							[NSNumber numberWithInt:f.origin.y], @"y",
							[NSNumber numberWithInt:f.size.width], @"width",
							[NSNumber numberWithInt:f.size.height], @"height",
						   nil, nil];
	
	[frames setObject:fdict forKey: name];
	
	[self saveUserSettings];
}

-(void) saveBreakpoints: (NSArray *) arr forWindow: (NSString *) name {
	if (!name)
		return;

	if (!userSettings)
		[self loadUserSettings];
	
	NSMutableDictionary *breakpoints = [userSettings objectForKey:@"breakpoints"];
	if (!breakpoints) {
		breakpoints = [NSMutableDictionary new];
		[userSettings setObject:breakpoints forKey:@"breakpoints"];
	}
	
	NSArray *copy = [NSArray arrayWithArray:arr];
	[breakpoints setObject: copy forKey: name];
	breakpointsChanged = true;
	[self saveUserSettings];
}

-(NSArray *) breakpointsForWindow:(NSString *) name {
	if (!name)
		return nil;
	if (!userSettings)
		[self loadUserSettings];
	
	NSDictionary *breakpoints = [userSettings objectForKey:@"breakpoints"];
	if (!breakpoints)
		return NO;
	
	return [breakpoints objectForKey:name];
}

-(bool) savedFrame: (NSRect *) pf forWindow: (NSString *) name {
	if (!userSettings)
		[self loadUserSettings];
	
	NSDictionary *frames = [userSettings objectForKey:@"frames"];
	if (!frames)
		return NO;

	NSDictionary *fdict = [frames objectForKey:name];
	if (!fdict)
		return NO;
	*pf = NSMakeRect([[fdict objectForKey:@"x"] intValue],
					 [[fdict objectForKey:@"y"] intValue],
					 [[fdict objectForKey:@"width"] intValue],
					 [[fdict objectForKey:@"height"] intValue]);
	return YES;
}

-(NSObject *) getProperty: (NSString *) propname {
	return [settings objectForKey:propname];
}

-(void) setProjectProperties: (NSDictionary *) newSettings {
	NSMutableDictionary *undoSettings = [NSMutableDictionary new];
	
	NSArray *keys = [newSettings allKeys];
	for (int idx = 0; idx < [keys count]; idx++) {
		NSString *key = [keys objectAtIndex:idx];
		NSObject *existing = [settings objectForKey:key];
		if (existing)
			[undoSettings setObject:existing forKey:key];
		else
			[undoSettings setObject:[NSNull null] forKey: key];
		
		NSObject *newVal = [newSettings objectForKey:key];
		if (newVal == [NSNull null])
			[settings removeObjectForKey:key];
		else
			[settings setObject:[newSettings objectForKey:key] forKey: key];
	}
	
	[[self undoManager] registerUndoWithTarget:self selector:@selector(setProjectProperties:) object:undoSettings];
}

-(id <CodeTarget> ) target {
	if ([self isWebProject])
		return [WebTarget sharedInstance];
	return [MacAppTarget sharedInstance];
}

-(void) errorSheetClose:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo {
}

-(void) showError: (NSString *) message withHeader: (NSString *) header {
	NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
	if (message) {
		NSAlert *alert = [[NSAlert alloc] init];
		[alert setMessageText:header];
		[alert setInformativeText:message];
		[alert addButtonWithTitle:@"Close"];
		[alert setAlertStyle:NSWarningAlertStyle];
		
		[alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(errorSheetClose:returnCode:contextInfo:) contextInfo:nil];
	}
}

-(void)showBuildSheet {
	ProjectWindowController *ctl = [[self windowControllers] objectAtIndex:0];
	[ctl showBuildSheet];
}

-(void)clearBuildSheet {
	ProjectWindowController *ctl = [[self windowControllers] objectAtIndex:0];
	[ctl clearBuildSheet];
}

-(BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem {
	SEL theAction = [anItem action];
	JavascriptProject *project = [JavascriptProject projectForDocument: self];
	
	if (theAction == @selector(debugRun:) ||
		theAction == @selector(buildApplication:)) {
		return YES;
	}
	if (theAction == @selector(debugStop:)) {
		return [self isDebugging];
	}
	if (theAction == @selector(stepInto:) ||
		theAction == @selector(stepOver:) ||
		theAction == @selector(stepOut:)) {
		return [self isDebugging] && [self isStopped];
	}
	
	return [super validateUserInterfaceItem:anItem];
}

@end
